import "./style.css";
/**
 * 模拟桌面端拖拽调整窗口大小的方式，调整元素大小
 * 在容器元素周围添加边缘元素结合鼠标事件实现。
 */

// 左边缘拖拽要注意：默认元素宽度的生长会在右侧
// 拖拽顶部边缘元素同理。
// 左边缘和下边缘则不需要，因为拖拽它们，本来就相对的是左上角，而元素默认生长方向就是从左上角开始。
// 所以这两个位置直接计算宽度高度即可。
// 注意：设置 left top 定位，相对的元素不同，定位值也不同（默认相对 document.body）

export default class ResizeByEdgeAndCorner {
    private container: HTMLElement; // 四条边和四个角的容器元素
    private relativePositionEl: HTMLElement; // 容器元素相对的定位元素，默认是 document.body，可以理解为画布区域
    private leftRelativeToPositionEl: number; // 容器左边缘相对于定位元素的距离
    private topRelativeToPositionEl: number; // 容器顶部边缘相对于定位元素的距离
    private minWidth: number = 200;
    private minHeight: number = 200;
    private lastPosition: {
        left: number;
        top: number;
        right: number;
        bottom: number;
    };

    constructor(
        container: HTMLElement,
        relativePositionEl: HTMLElement = document.body,
        minWidth = 200,
        minHeight = 200
    ) {
        this.container = container;
        this.relativePositionEl = relativePositionEl;
        this.minWidth = minWidth;
        this.minHeight = minHeight;

        const { left: leftOfContianerRelToViewport, top: topOfContianerRelToViewport } =
            container.getBoundingClientRect();
        const { left, top } = relativePositionEl.getBoundingClientRect();
        this.leftRelativeToPositionEl = leftOfContianerRelToViewport - left;
        this.topRelativeToPositionEl = topOfContianerRelToViewport - top;

        // container 容器元素相对调整区域元素 relativePositionEl 的定位信息
        this.lastPosition = {
            left: this.leftRelativeToPositionEl,
            top: this.topRelativeToPositionEl,
            right: relativePositionEl.offsetWidth - (this.leftRelativeToPositionEl + container.offsetWidth),
            bottom: relativePositionEl.clientHeight - (this.topRelativeToPositionEl + container.offsetHeight)
        };

        this.run(this.container, this.relativePositionEl, this.minWidth, this.minHeight);
    }

    private run(container: HTMLElement, relativePositionEl: HTMLElement, minWidth = 200, minHeight = 200) {
        // 边元素拖拽调整元素大小
        const edgeRight = document.querySelector(".edge-right") as HTMLDivElement;
        const edgeLeft = document.querySelector(".edge-left") as HTMLDivElement;
        const edgeTop = document.querySelector(".edge-top") as HTMLDivElement;
        const edgeBottom = document.querySelector(".edge-bottom") as HTMLDivElement;
        this.resizeByDragEdge(container, edgeRight, "right", relativePositionEl, minWidth, minHeight);
        this.resizeByDragEdge(container, edgeLeft, "left", relativePositionEl, minWidth, minHeight);
        this.resizeByDragEdge(container, edgeTop, "top", relativePositionEl, minWidth, minHeight);
        this.resizeByDragEdge(container, edgeBottom, "bottom", relativePositionEl, minWidth, minHeight);

        // 角元素拖拽调整元素大小
        this.resizeByDragCorner(
            container,
            document.querySelector(".corner-rb") as HTMLDivElement,
            "right-bottom",
            relativePositionEl,
            minWidth,
            minHeight
        );
        this.resizeByDragCorner(
            container,
            document.querySelector(".corner-rt") as HTMLDivElement,
            "right-top",
            relativePositionEl,
            minWidth,
            minHeight
        );
        this.resizeByDragCorner(
            container,
            document.querySelector(".corner-lt") as HTMLDivElement,
            "left-top",
            relativePositionEl,
            minWidth,
            minHeight
        );
        this.resizeByDragCorner(
            container,
            document.querySelector(".corner-lb") as HTMLDivElement,
            "left-bottom",
            relativePositionEl,
            minWidth,
            minHeight
        );
    }

    /**
     * 根据拖拽的边缘元素的位置，返回对应的调整元素大小的 mousemove 事件处理函数
     * @param containerEl - 容器元素
     * @param position - 拖拽元素的位置
     * @param relativePositionEl - 相对定位的元素，默认是 document.body
     * @param minWidth - 最小宽度
     * @param minHeight - 最小高度
     * @returns 对应位置调整元素大小的函数
     */
    private moveFactory = (
        containerEl: HTMLElement,
        position: "left" | "right" | "top" | "bottom",
        relativePositionEl: HTMLElement = document.body,
        minWidth = 200,
        minHeight = 200
    ) => {
        const handlerMap = {
            left: (e: MouseEvent) => {
                // 防止左边出界
                if (containerEl.offsetLeft < -10) return;

                // 固定右边
                // 注意：top 和 left 的优先级比 bottom right 要高
                // 问题：先拖拽右侧边时设置的固定 left 会导致后续拖拽左侧边失效，宽还是向右生长
                // 原因：当拖拽左侧边时，先前设置的 left 和 当前要固定的 right 共存了 ，导致 right 固定失效
                // 解决：设置 left 为默认值 auto，让 right 固定生效。
                containerEl.style.left = `auto`;
                containerEl.style.right = `${this.lastPosition.right}px`;
                // 实时计算元素移动的微小距离
                const { left, width } = containerEl.getBoundingClientRect();
                const moveDistance = left - e.clientX; // 移动距离，往左是正数，往右是负数
                // 实时计算元素的新宽度
                const newWidth = width + moveDistance;
                containerEl.style.width = `${Math.max(minWidth, newWidth)}px`;

                // 更新拖动后的位置信息 left
                this.lastPosition.left = Math.max(0, containerEl.offsetLeft);
            },

            right: (e: MouseEvent) => {
                // 固定左边
                containerEl.style.right = `auto`;
                containerEl.style.left = `${this.lastPosition.left}px`;

                const { left } = containerEl.getBoundingClientRect();
                const newWidth = e.clientX - left; // 鼠标视口位置相对左上角直接计算新的宽度
                containerEl.style.width = `${Math.max(minWidth, newWidth)}px`;

                // 更新拖动后的位置信息 right
                this.lastPosition.right = Math.max(
                    0,
                    relativePositionEl.clientWidth - (containerEl.offsetLeft + containerEl.offsetWidth)
                );
            },
            top: (e: MouseEvent) => {
                if (containerEl.offsetTop < 0) return;
                // 固定底部
                containerEl.style.top = `auto`;
                containerEl.style.bottom = `${this.lastPosition.bottom}px`;

                const { top, height } = containerEl.getBoundingClientRect();
                const moveDistance = top - e.clientY; // 移动距离，往上是正数，往下是负数
                const newHeight = height + moveDistance;
                containerEl.style.height = `${Math.max(minHeight, newHeight)}px`;

                // 更新拖动后的位置信息 top
                this.lastPosition.top = Math.max(0, containerEl.offsetTop);
            },
            bottom: (e: MouseEvent) => {
                // 固定顶部
                containerEl.style.bottom = `auto`;
                containerEl.style.top = `${this.lastPosition.top}px`;

                const { top } = containerEl.getBoundingClientRect();
                const newHeight = e.clientY - top;
                containerEl.style.height = `${Math.max(minHeight, newHeight)}px`;
                // 更新拖动后的位置信息 bottom
                this.lastPosition.bottom = Math.max(
                    0,
                    relativePositionEl.clientHeight - (containerEl.offsetTop + containerEl.offsetHeight)
                );
            }
        };
        return handlerMap[position];
    };

    /**
     * 拖拽容器元素的四条边元素去调整元素大小
     * @param containerEl - 容器元素
     * @param edgeEL - 拖拽的边元素
     * @param edgeELPosition - 边元素的位置
     * @param relativePositionEl - 相对定位的元素，默认是 document.body
     * @param minWidth - 最小宽度
     * @param minHeight - 最小高度
     */
    private resizeByDragEdge = (
        containerEl: HTMLElement,
        edgeEL: HTMLElement,
        edgeELPosition: "left" | "right" | "top" | "bottom",
        relativePositionEl: HTMLElement = document.body,
        minWidth = 200,
        minHeight = 200
    ) => {
        // 鼠标事件实现拖拽固定结构
        edgeEL.addEventListener("mousedown", e => {
            e.preventDefault();
            e.stopPropagation();
            relativePositionEl.addEventListener("mousemove", handleMouseMove);
            relativePositionEl.addEventListener("mouseup", clearListener);
            console.log("mousedown");
        });
        const handleMouseMove = (e: MouseEvent) => {
            e.preventDefault();
            e.stopPropagation();
            this.moveFactory(containerEl, edgeELPosition, relativePositionEl, minWidth, minHeight)(e);
            console.log("mousemove");
        };

        function clearListener(e: MouseEvent) {
            e.preventDefault();
            e.stopPropagation();
            relativePositionEl.removeEventListener("mousemove", handleMouseMove);
            relativePositionEl.removeEventListener("mouseup", clearListener);
        }
    };

    /**
     * 拖拽容器元素的四个角元素去调整元素大小
     * @param containerEl - 容器元素
     * @param cornerEL - 拖拽的角元素
     * @param cornerELPosition - 角元素的位置
     * @param relativePositionEl - 相对定位的元素，默认是 document.body
     * @param minWidth - 最小宽度
     * @param minHeight - 最小高度
     */
    private resizeByDragCorner = (
        containerEl: HTMLElement,
        cornerEL: HTMLElement,
        cornerELPosition: "left-top" | "right-top" | "left-bottom" | "right-bottom",
        relativePositionEl: HTMLElement = document.body,
        minWidth = 200,
        minHeight = 200
    ) => {
        // 鼠标事件实现拖拽固定结构
        cornerEL.addEventListener("mousedown", e => {
            e.stopPropagation();
            e.preventDefault();
            relativePositionEl.addEventListener("mousemove", handleMouseMove);
            relativePositionEl.addEventListener("mouseup", clearListener);
        });

        const handleMouseMove = (e: MouseEvent) => {
            e.stopPropagation();
            e.preventDefault();

            // 调用对应位置的moveFactory函数
            // 拖动一个角，也就是同时调整两个方向的大小，调用两个对应方向的处理函数即可
            cornerELPosition.split("-").forEach((position: any) => {
                this.moveFactory(containerEl, position, relativePositionEl, minWidth, minHeight)(e);
            });
        };

        function clearListener(e: MouseEvent) {
            e.stopPropagation();
            e.preventDefault();

            relativePositionEl.removeEventListener("mousemove", handleMouseMove);
            relativePositionEl.removeEventListener("mouseup", clearListener);
        }
    };
}
